/*
 * Powered By [lixin]
 * 代码脚手架工具生成 [AI-Code]
 */

package com.rzhkj.box.service.impl;

import com.alibaba.fastjson.JSON;
import com.baidu.fsg.uid.UidGenerator;
import com.beust.jcommander.internal.Maps;
import com.rzhkj.box.dao.*;
import com.rzhkj.box.entity.*;
import com.rzhkj.box.service.DocSV;
import com.rzhkj.core.base.BaseMybatisDAO;
import com.rzhkj.core.base.BaseMybatisSVImpl;
import com.rzhkj.core.enums.YNEnum;
import com.rzhkj.core.exceptions.BaseException;
import com.rzhkj.core.exceptions.DocException;
import com.rzhkj.core.tools.*;
import com.rzhkj.setting.dao.SettingDAO;
import com.rzhkj.setting.entity.Setting;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.io.FileUtils;
import org.springframework.stereotype.Component;
import org.springframework.stereotype.Service;
import org.springframework.util.Base64Utils;

import javax.annotation.Resource;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Map;


@Component
@Service
@Slf4j
public class DocSVImpl extends BaseMybatisSVImpl<Doc, Long> implements DocSV {

    @Resource
    private DocDAO docDAO;

    @Resource
    private DocLogDAO docLogDAO;

    @Resource
    private BoxDAO boxDAO;

    @Resource
    private NotificationDAO notificationDAO;
    @Resource
    private NotificationLogDAO notificationLogDAO;

    @Resource
    private SettingDAO settingDAO;


    @Resource
    private UidGenerator uidGenerator;

    @Override
    protected BaseMybatisDAO getBaseMybatisDAO() {
        return docDAO;
    }


    /**
     * 上传文档
     * 1.查询box
     * 2.上传文档
     * 3.存储上传文档信息
     * 4.回调接口
     *
     * @param boxFile 收纳文件
     * @param boxCode 收纳箱编码
     * @param file    文档
     * @param suffix  后缀
     * @param size    大小
     * @return true/false
     */
    @Override
    public boolean uploadDoc(BoxFile boxFile, String boxCode, InputStream file, String suffix, Long size) {
        //1.查询box
        Map<String, Object> params = Maps.newHashMap();
        params.put("code", boxCode);
        Box box = boxDAO.load(params);
        if (box == null) {
            log.error(BaseException.BaseExceptionEnum.Result_Not_Exist.toString());
            throw new DocException(BaseException.BaseExceptionEnum.Result_Not_Exist);
        }
        if (boxFile.getNamespace() == null) {
            boxFile.setNamespace(box.getNamespace());
        }
        if (boxFile.getCompressType() == null) {
            boxFile.setCompressType(box.getCompressType());
        }

        //2.上传文档
        String path = null;
        String fileName=null;
        if (boxFile.getCustomFileName() != null) {
            fileName = boxFile.getCustomFileName();
        } else {
            fileName = String.valueOf(uidGenerator.getUID());
        }
        //path namespace/yyyyMM/uid.suffix
        path = box.getNamespace() + "/" + DateTools.yyyyMM(new Date()) + "/" + fileName + "." + suffix;
        path = path.replace("//", "/").replace("..", ".");
        try {
            String workspace = settingDAO.loadByKey(Setting.Key.Workspace.name()).getV();
            workspace = workspace + path;
            workspace = workspace.replace("//", "/");
            FileUtils.copyInputStreamToFile(file, new File(workspace));
        } catch (IOException e) {
            e.printStackTrace();
            log.error(BaseException.BaseExceptionEnum.Result_Not_Exist.toString());
            throw new DocException(BaseException.BaseExceptionEnum.Upload_Error);
        }

        //3.存储上传文档信息
        Doc doc = this.saveDoc(boxCode, box.getNamespace(), path, size.intValue(), suffix, boxFile.getCustomFileName(),
                fileName, boxFile.getMediaType(), boxFile.isCover(), boxFile.isCache());

        //4.回调接口
        this.notify(boxFile, doc.getCode(), path, null);
        return true;
    }


    /**
     * 上传图片
     * 1.根据api 信息查询box信息
     * 2.上传图片
     *
     * @param boxFile 文件对象
     * @param boxCode 收纳盒编码
     * @param pic     图片流
     * @param suffix  后缀
     * @param size    大小
     * @return true/false
     */
    @Override
    public boolean uploadPic(BoxFile boxFile, String boxCode, InputStream pic, String suffix, Long size) {
        //1.根据api 信息查询box信息
        if (boxFile == null) {
            log.error(BaseException.BaseExceptionEnum.Ilegal_Param.toString());
            throw new DocException(BaseException.BaseExceptionEnum.Ilegal_Param);
        }
        Map<String, Object> params = Maps.newHashMap();
        params.put("code", boxCode);
        Box box = boxDAO.load(params);
        if (box == null) {
            log.error(BaseException.BaseExceptionEnum.Result_Not_Exist.toString());
            throw new DocException(BaseException.BaseExceptionEnum.Result_Not_Exist);
        }
        if (boxFile.getNamespace() == null) {
            boxFile.setNamespace(box.getNamespace());
        }
        if (boxFile.getCompressType() == null) {
            boxFile.setCompressType(box.getCompressType());
        }

        //2.上传图片
        this.upload(boxFile, pic, boxCode);

        return true;
    }

    /**
     * 上传base64图片
     * 1.信息查询box信息
     * 2.过滤上传类型限制
     * 3.上传图片
     *
     * @param boxFiles 文件对象
     * @param boxCode  box编码
     * @return true/false
     */
    @Override
    public boolean uploadBase64Pic(List<BoxFile> boxFiles, String boxCode) {
        //1.信息查询box信息
        Map<String, Object> params = Maps.newHashMap();
        params.put("code", boxCode);
        Box box = boxDAO.load(params);
        if (box == null) {
            log.error(BaseException.BaseExceptionEnum.Result_Not_Exist.toString());
            throw new DocException(BaseException.BaseExceptionEnum.Result_Not_Exist);
        }

        //2.过滤上传类型限制
        Map<String, MediaType> mediaLimitMap = box.mediaLimitMap();
        for (BoxFile boxFile : boxFiles) {
            if (!mediaLimitMap.isEmpty()) {
                if (!mediaLimitMap.containsKey(MediaType.getEnum(boxFile.getMediaType()).name())) {
                    if (mediaLimitMap.containsKey("!" + MediaType.getEnum(boxFile.getMediaType()).name())) {
                        log.error(BaseException.BaseExceptionEnum.Media_Type_Limit_Error.error);
                        throw new DocException(BaseException.BaseExceptionEnum.Media_Type_Limit_Error);
                    } else {
                        log.error(BaseException.BaseExceptionEnum.Media_Type_Not_Exist_Error.error);
                        throw new DocException(BaseException.BaseExceptionEnum.Media_Type_Not_Exist_Error);
                    }
                }
            }
        }

        //3.上传图片
        for (BoxFile boxFile : boxFiles) {
            if (boxFile.getNamespace() == null) {
                boxFile.setNamespace(box.getNamespace());
            }
            byte[] bytes = Base64Utils.decodeFromString(boxFile.getFile());
            ByteArrayInputStream in = new ByteArrayInputStream(bytes);
            if (boxFile.getNamespace() == null) {
                boxFile.setNamespace(box.getNamespace());
            }
            if (boxFile.getCompressType() == null) {
                boxFile.setCompressType(box.getCompressType());
            }
            this.upload(boxFile, in, boxCode);
        }

        return true;
    }

    //上传图片
    private void upload(BoxFile boxFile, InputStream inputStream, String boxCode) {
        String workspace = settingDAO.loadByKey(Setting.Key.Workspace.name()).getV();
        String path = null;
        String fileName = null;
        if (boxFile.getCustomFileName() != null) {
            fileName = boxFile.getCustomFileName();
        } else {
            fileName = this.genImageFileName(boxFile.getNamespace(), boxFile.suffix());
        }
        //path namespace/yyyyMM/uid.suffix
        path = boxFile.getNamespace() + "/" + DateTools.yyyyMM(new Date()) + "/" + fileName;
        path = path.replace("//", "/").replace("..", ".");
        workspace = workspace + path;
        workspace = workspace.replace("//", "/");
        File toUploadFile = new File(workspace);
        try {
            FileUtils.copyInputStreamToFile(inputStream, toUploadFile);
        } catch (IOException e) {
            e.printStackTrace();
            log.error(BaseException.BaseExceptionEnum.Upload_Error.toString());
            throw new DocException(BaseException.BaseExceptionEnum.Upload_Error);
        }

        List<String> picNameList = null;
        try {
            //4.图片压缩小图处理  32x32 50x50 100x100
            picNameList = this.compressPic(boxFile, workspace);
        } catch (IOException e) {
            e.printStackTrace();
            log.error(BaseException.BaseExceptionEnum.Compress_Image_Error.toString());
            throw new DocException(BaseException.BaseExceptionEnum.Compress_Image_Error);
        }

        //5.记录上传图片及压缩小图信息
        Doc doc = this.saveDoc(boxCode, boxFile.getNamespace(), path, boxFile.genSize(), boxFile.suffix(), boxFile.getCustomFileName(), fileName,
                boxFile.getMediaType(), boxFile.isCover(), boxFile.isCache());

        //6.回调接口
        this.notify(boxFile, doc.getCode(), path, picNameList);
    }

    //生成文件名
    private String genImageFileName(String nameSpace, String suffix) {
        String imageFileNameTpl = settingDAO.loadByKey(Setting.Key.ImageFileNameTpl.name()).getV();
        if (imageFileNameTpl == null) {
            log.error(BaseException.BaseExceptionEnum.Result_Not_Exist.error);
            throw new DocException(BaseException.BaseExceptionEnum.Result_Not_Exist);
        }
        String fileName = imageFileNameTpl.replace("{{namespace}}", nameSpace)
                .replace("{{ddHHmmssSSS}}", DateTools.format(new Date(), "ddHHmmssSSS"))
                .replace("{{uid}}", UuidTools.getUUIDString())
                .replace("{{suffix}}", suffix);
        return fileName;
    }

    //图片压缩小图处理  32x32 50x50 100x100
    private List<String> compressPic(BoxFile boxFile, String workspace) throws IOException {
        List<String> picNameList = null;
        if (boxFile.listCompressType() != null) {
            Setting compressTypeSetting = settingDAO.loadByKey(Setting.Key.CompressType.name());
            List<String> compressTypeList = compressTypeSetting.listCompressType();
            for (String compressType : compressTypeList) {
                for (String compressTypeBoxFile : boxFile.listCompressType()) {
                    if (compressType.equals(compressTypeBoxFile)) {
                        int width = 100;
                        int height = 100;
                        if (compressType.indexOf("x") > 0) {
                            width = Integer.parseInt(compressTypeBoxFile.substring(0, compressType.indexOf("x")));
                            height = Integer.parseInt(compressTypeBoxFile.substring(compressType.indexOf("x") + 1));
                        }

                        String picName = workspace + "_" + compressType + "." + boxFile.suffix();
                        CompressPic.INSTANCE.compressPic(new File(workspace), picName, width, height);
                        if (picNameList == null) {
                            picNameList = new ArrayList<>();
                        }
                        picNameList.add(picName);
                    }
                }
            }
        }
        return picNameList;
    }

    //保存文档信息
    private Doc saveDoc(String boxCode, String nameSpace, String path, Integer size, String suffix, String customFileName,
                        String fileName, String type, boolean isCover, boolean isCache) {
        Doc doc = new Doc();
        doc.setCode(String.valueOf(uidGenerator.getUID()));
        doc.setBoxCode(boxCode);
        doc.setNamespace(nameSpace);
        doc.setIsCustomFileName(customFileName != null ? YNEnum.Y.name() : YNEnum.N.name());
        doc.setCustomFileName(customFileName != null ? customFileName : null);
        doc.setFileName(customFileName != null ? null : fileName);
        doc.setType(type);
        doc.setIsCache(isCache ? YNEnum.Y.name() : YNEnum.N.name());
        doc.setIsCover(isCover ? YNEnum.Y.name() : YNEnum.N.name());
        doc.setPath(path);
        doc.setSize(size);
        doc.setSuffix(suffix);
        doc.setState(DocStateEnum.Enable.name());
        doc.setCreateTime(new Date());
        doc.setUpdateTime(new Date());
        docDAO.insert(doc);

        DocLog docLog = new DocLog();
        docLog.setCreateTime(new Date());
        docLog.setDocCode(doc.getCode());
        docLog.setLog("上传文件" + path);
        docLogDAO.insert(docLog);
        return doc;
    }

    //回调通知
    private void notify(BoxFile boxFile, String docCode, String path, List<String> picNameList) {
        Executors.fixedThreadExecutor(new Runnable() {
            @Override
            public void run() {
                String domain = settingDAO.loadByKey(Setting.Key.Domain.name()).getV();
                Map<String, Object> param = null;
                if (boxFile.getNotifyData() != null) {
                    //设置魔法值
                    String magicValue = magicValue(boxFile.getNotifyData());
                    param = JSON.parseObject(magicValue, Map.class);
                    param.put("paths", picNameList);
                }
                if (picNameList != null && picNameList.size() > 0) {
                    for (String picName : picNameList) {
                        picName = (domain + "/" + picName).replace("//", "/");
                    }
                }
                //回调
                List<String> notifyUrlList = boxFile.listNotifyUrl();
                for (String notifyUrl : notifyUrlList) {
                    //回调接口记录
                    Notification notification = new Notification();
                    notification.setDocCode(docCode);
                    notification.setCode(String.valueOf(uidGenerator.getUID()));
                    notification.setNotifyUrl(notifyUrl);
                    notification.setNum(1);
                    notification.setNotifyData(JSON.toJSONString(param));
                    notification.setNotifyContentType(boxFile.getNotifyContentType() == null ? ContentTypeEnum.JSON.val : ContentTypeEnum.X_WWW_FORM_URLENCODED.val);
                    notification.setNotifyRule(NotificationRuleEnum.Second5.val);
                    notification.setState(NotificationStateEnum.Create.name());
                    notification.setNextNotifyTime(DateTools.calculateBySecond(new Date(), Integer.parseInt(NotificationRuleEnum.Second5.val)));
                    notification.setCreateTime(new Date());
                    notification.setUpdateTime(new Date());
                    notificationDAO.insert(notification);

                    //TODO 增加form 表单提交
                    Map<String, String> result = HttpTools.notify(notifyUrl, param);
                    //记录回调信息
                    NotificationLog notificationLog = new NotificationLog();
                    notificationLog.setHttpState(result.get("code"));
                    notificationLog.setNotifyResult(result.get("result"));
                    notificationLog.setNotificationCode(notification.getCode());
                    notificationLog.setNotifycationUrl(notifyUrl);
                    notificationLog.setCreateTime(new Date());
                    notificationLogDAO.insert(notificationLog);
                }

            }
        });
    }

    /**
     * 魔法值
     * 通过${xx}获取系统预定义的一些值，这些值默认不对外返回，只有明确
     * 通过${}取值时才会返回
     * 公式
     * ${size} 获取大小
     * ${path} 获取路径
     *
     * @param notifyData 通知数据值
     * @return
     */
    private String magicValue(String notifyData) {
        if (notifyData == null) {
            return null;
        }
//        if (notifyData.contains("${size}") || notifyData.contains("{{size}}")) {
//            notifyData = notifyData.replace("${size}", );
//        }
//        if (notifyData.contains("${}")) {
//
//        }

        return notifyData;

    }

    /**
     * 加载对象Doc 通过code
     *
     * @param code 编码
     * @return Doc
     */
    @Override
    public Doc loadByCode(java.lang.String code) {
        return docDAO.loadByCode(code);
    }

    /**
     * 加载对象Doc 通过boxCode
     *
     * @param boxCode 收纳盒编码
     * @return Doc
     */
    @Override
    public Doc loadByBoxCode(java.lang.String boxCode) {
        return docDAO.loadByBoxCode(boxCode);
    }

    /**
     * 删除对象Doc
     *
     * @param id id* @param code 编码* @param boxCode 收纳盒编码
     * @return Doc
     */
    @Override
    public void delete(java.lang.Long id, java.lang.String code, java.lang.String boxCode) {
        docDAO.delete(id, code, boxCode);
    }
}
